package com.iwhalecloud.citybrain.flow.platform.manager.application.service.impl;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.OuterDataSourceService;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.JsonUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.common.ResponseResult;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.form.FormProcessInstanceTaskRelVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.form.OptionItem;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.form.OuterSourceRequestVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FormProcessInstanceTaskRelRepository;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import org.apache.commons.collections4.CollectionUtils;
import org.flowable.form.model.FormFieldTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author lilh
 * @date 2020/12/8 上午11:09
 */
@Service
public class OuterDataSourceServiceImpl implements OuterDataSourceService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private final Map<String, Method> methodMap = new HashMap<>(16);

    @Autowired
    private FormProcessInstanceTaskRelRepository formProcessInstanceTaskRelRepository;

    @Override
    public Object execute(OuterSourceRequestVo requestVo) {
        Method requestMethod = resolveRequestMethod(requestVo.getRequestMethod());
        if (Objects.isNull(requestMethod)) {
            throw new IllegalArgumentException("参数异常");
        }
        logger.info("请求地址为: {}, 方法为：{}", requestVo.getUrl(), requestVo.getRequestMethod());
        HttpRequest request = HttpUtil.createRequest(requestMethod, requestVo.getUrl()).header(resolveHeaders(requestVo.getHeaders()));
        if (isPost(requestMethod)) {
            request.body("{}");
        }
        try {
            HttpResponse response = request.execute();
            logger.info("返回结果为：{}", response.toString());
            if (response.isOk()) {
                Object resultObject = JSONObject.parseArray(response.body());
                JSONObject result = new JSONObject();
                result.put("list", resultObject);
                return ResponseResult.success(result);
            }

        } catch (Exception e) {
            logger.error("请求失败：{}", e.getLocalizedMessage());
        }

        throw new IllegalArgumentException("请求失败");
    }

    @Override
    public List<OptionItem> preRequestData(OuterSourceRequestVo requestVo) {
        Object jsonResult = null;
        try {
            jsonResult = this.execute(requestVo);
        } catch (Exception e) {
            logger.warn("请求失败：{}", e.getLocalizedMessage());
            return Collections.emptyList();
        }
        Configuration conf = Configuration.defaultConfiguration();
        conf = conf.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);

        String json = JSONObject.toJSONString(jsonResult);
        logger.info("json数据为:{}", json);
        DocumentContext context = JsonPath.using(conf).parse(json);
        List<String> ids = context.read(transferName(requestVo.getIdPath()));
        List<String> values = context.read(transferName(requestVo.getValuePath()));
        if (CollectionUtils.isEmpty(ids) || CollectionUtils.isEmpty(values)
                || !Objects.equals(ids.size(), values.size())) {
            return Collections.emptyList();
        }
        List<OptionItem> items = new ArrayList<>(ids.size());
        for (int i = 0; i < ids.size(); i++) {
            OptionItem item = new OptionItem();
            item.setId(ids.get(i));
            item.setName(values.get(i));
            items.add(item);
        }
        return items;
    }

    @Override
    public List<Object> processDataSourceConfig(List<Object> fields) {
        List<Object> result = new ArrayList<>(fields.size());
        for (Object field : fields) {
            JSONObject formObject = JSON.parseObject(JsonUtils.toJson(field));
            JSONObject params = formObject.getJSONObject("params");
            if (Objects.isNull(params)) {
                result.add(field);
                continue;
            }

            if (!canApply(formObject, params)) {
                result.add(field);
                continue;
            }

            // apply
            formObject.remove("options");
            OuterSourceRequestVo config = params.toJavaObject(OuterSourceRequestVo.class);
            List<OptionItem> optionItems = this.preRequestData(config);
            formObject.put("options", optionItems);

            result.add(formObject.toJavaObject(Object.class));

        }
        return result;
    }

    @Override
    public List<Object> processDataSourceConfigAfterSubmit(List<Object> fields, String processInstanceId, String taskId) {
        List<FormProcessInstanceTaskRelVo> formFieldData = resolveFormFieldData(processInstanceId, taskId);
        if (CollectionUtils.isEmpty(formFieldData)) {
            return fields;
        }
        Map<String, byte[]> fieldIdToDataMap = formFieldData.stream().collect(Collectors.toMap(FormProcessInstanceTaskRelVo::getFormFieldId, FormProcessInstanceTaskRelVo::getFormDataByte));
        List<Object> result = new ArrayList<>(fields.size());
        for (Object field : fields) {
            JSONObject fieldObject = JSON.parseObject(JsonUtils.toJson(field));
            JSONObject params = fieldObject.getJSONObject("params");

            if (Objects.isNull(params) || !canApply(fieldObject, params)) {
                result.add(field);
                continue;
            }

            // apply
            String fieldId = fieldObject.getString("id");
            if (Objects.nonNull(fieldIdToDataMap.get(fieldId))) {
                List<OptionItem> optionItems = JSONObject.parseArray(new String(fieldIdToDataMap.get(fieldId), StandardCharsets.UTF_8), OptionItem.class);
                fieldObject.put("options", optionItems);
            }

            result.add(fieldObject.toJavaObject(Object.class));

        }
        return result;
    }

    @Override
    public boolean hasSubmitDataSourceConfig(String processInstanceId, String taskId) {
        return CollectionUtils.isNotEmpty(resolveFormFieldData(processInstanceId, taskId));
    }

    private List<FormProcessInstanceTaskRelVo> resolveFormFieldData(String processInstanceId, String taskId) {
        if (StrUtil.isNotBlank(processInstanceId)) {
            return formProcessInstanceTaskRelRepository.getStartFormInfo(processInstanceId);
        }

        if (StrUtil.isNotBlank(taskId)) {
            return formProcessInstanceTaskRelRepository.getTaskFormInfo(taskId);
        }
        return Collections.emptyList();
    }

    private boolean canApply(JSONObject formObject, JSONObject params) {
        String type = formObject.getString("type");
        String url = params.getString("url");
        //没有配置外部数据源，则直接不处理
        if (StrUtil.isBlank(url)) {
            return false;
        }
        //只处理下拉，多选，单选三种类型的控件
        return Arrays.asList(FormFieldTypes.BOOLEAN, FormFieldTypes.RADIO_BUTTONS, FormFieldTypes.DROPDOWN).contains(type);
    }

    private String transferName(String path) {
        return "$." + path.replaceAll("/", ".");
    }

    private boolean isPost(Method requestMethod) {
        return Method.POST == requestMethod;
    }

    private Map<String, List<String>> resolveHeaders(String header) {
        Map<String, List<String>> headers = new HashMap<>(16);
        if (!StrUtil.isBlank(header)) {
            List<String> pairs = StrUtil.split(header, ';');
            if (CollectionUtils.isNotEmpty(pairs)) {
                pairs.stream().filter(item -> item.contains("=")).forEach(item -> {
                    List<String> keyValue = StrUtil.split(item, '=');
                    if (CollectionUtils.isNotEmpty(keyValue) && keyValue.size() == 2) {
                        if (Objects.isNull(headers.get(keyValue.get(0)))) {
                            headers.put(keyValue.get(0), new ArrayList<>());
                        }
                        headers.get(keyValue.get(0)).add(keyValue.get(1));
                    }
                });
            }
        }
        return headers;
    }

    private Method resolveRequestMethod(String requestMethod) {
        if (StrUtil.isBlank(requestMethod)) {
            return null;
        }

        return methodMap.get(requestMethod);
    }

    @PostConstruct
    public void init() {
        for (Method method : Method.values()) {
            methodMap.put(method.name(), method);
        }
    }
}
